/*
 *  Copyright 2016 Google Inc. All Rights Reserved.
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.google.blockly.ohos.ui.fieldview;

import com.google.blockly.model.Field;
import com.google.blockly.model.FieldColor;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.window.dialog.PopupDialog;
import ohos.app.Context;
import ohos.global.configuration.DeviceCapability;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

/**
 * Renders a color field and picker as part of a BlockView.
 */
public class BasicFieldColorView extends StackLayout implements FieldView, Component.EstimateSizeListener {
    protected static final int DEFAULT_MIN_WIDTH_DP = 40;
    protected static final int DEFAULT_MIN_HEIGHT_DP = 28;  // Base on styled label text height?

    static final int ALPHA_OPAQUE = 0xFF000000;

    private Field.Observer mFieldObserver = new Field.Observer() {
        @Override
        public void onValueChanged(Field field, String oldValue, String newValue) {
            setColor(mColorField.getColor());
        }
    };

    protected FieldColor mColorField = null;

    protected AutoPositionPopupWindow mColorPopupWindow;
    protected ColorPaletteView mColorPaletteView;

    public BasicFieldColorView(Context context) {
        super(context);
        initPostConstructor();
    }

    public BasicFieldColorView(Context context, AttrSet attrs) {
        super(context, attrs);
        initPostConstructor();
    }

    private void initPostConstructor() {
        setEstimateSizeListener(this);

        float density = getContext().getResourceManager().getDeviceCapability().screenDensity / DeviceCapability.SCREEN_MDPI;
        setMinWidth((int) (DEFAULT_MIN_WIDTH_DP * density));
        setMinHeight((int) (DEFAULT_MIN_HEIGHT_DP * density));

        setClickedListener(v -> openColorPickerPopupWindow());
    }

    @Override
    public void setField(Field field) {
        FieldColor colorField = (FieldColor) field;
        if (mColorField == colorField) {
            return;
        }

        if (mColorField != null) {
            mColorField.unregisterObserver(mFieldObserver);
        }
        mColorField = colorField;
        if (mColorField != null) {
            setColor(mColorField.getColor());
            mColorField.registerObserver(mFieldObserver);
        }
    }

    @Override
    public Field getField() {
        return mColorField;
    }

    @Override
    public void unlinkField() {
        setField(null);
    }

    /**
     * Set the selected color represented by this view.  The alpha values will be ignored for
     * rendering.
     *
     * @param color The color in {@code int} format.
     */
    public void setColor(int color) {
        ShapeElement shapeElement = new ShapeElement();
        shapeElement.setRgbColor(RgbColor.fromArgbInt(ALPHA_OPAQUE | color));
        setBackground(shapeElement);
    }

    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        setEstimatedSize(onMeasureDimension(getMinWidth(), widthMeasureSpec),
                onMeasureDimension(getMinHeight(), heightMeasureSpec));
        return true;
    }

    /**
     * Calculates the measured dimension in a single direction (width or height).
     *
     * @param min         The minimum size.
     * @param measureSpec The {@link Component.EstimateSpec} provided by the parent.
     * @return The calculated size.
     */
    protected int onMeasureDimension(int min, int measureSpec) {
        switch (EstimateSpec.getMode(measureSpec)) {
            case EstimateSpec.PRECISE:
                return EstimateSpec.getSizeWithMode(EstimateSpec.getSize(measureSpec), EstimateSpec.PRECISE);
            case EstimateSpec.NOT_EXCEED:
                return EstimateSpec.getSizeWithMode(Math.min(min, EstimateSpec.getSize(measureSpec)), EstimateSpec.PRECISE);
            case EstimateSpec.UNCONSTRAINT:
            default:
                return EstimateSpec.getSizeWithMode(min, EstimateSpec.PRECISE);
        }
    }

    /**
     * Open a {@link PopupDialog} showing a color selection palette.
     */
    protected void openColorPickerPopupWindow() {
        if (mColorPaletteView == null) {
            mColorPaletteView = new ColorPaletteView(this);
        }

        if (mColorPopupWindow == null) {
            mColorPopupWindow = new AutoPositionPopupWindow(mColorPaletteView);
        }

        mColorPopupWindow.showDialog();
    }

    /**
     * Popup window that adjusts positioning to the size of the wrapped view.
     */
    protected class AutoPositionPopupWindow extends PopupDialog {
        private final Component mWrapView;

        /**
         * Construct popup window wrapping an existing {@link Component} object.
         *
         * @param wrapView The view shown inside the popup window.
         */
        public AutoPositionPopupWindow(Component wrapView) {
            super(mContext, BasicFieldColorView.this);
            mWrapView = wrapView;
            // This is necessary because PopupWindow responds to touch events only with
            // background != null.
            setBackground(new ShapeElement());
            setCustomComponent(mWrapView);
            setDialogListener(() -> {
                hide();
                return false;
            });
        }

        /**
         * Show popup window next to an anchor view.
         */
        public void showDialog() {
            // Get size of the wrapped view. Since it may not have been measured yet, allow fallback
            // to minimum size.
            int width = mWrapView.getEstimatedWidth();
            if (width == 0) {
                // Note - getMinimumWidth/Height require API 16 and above.
                width = mWrapView.getMinWidth();
            }

            int height = mWrapView.getEstimatedHeight();
            if (height == 0) {
                height = mWrapView.getMinHeight();
            }

            // Set size of popup window to match wrapped content; this will allow automatic
            // positioning to fit on screen.
            setWidth(width);
            setHeight(height);

            show();
        }
    }

    /**
     * View for a color palette that matches Web Blockly's.
     */
    protected class ColorPaletteView extends Component implements DrawTask, EstimateSizeListener, TouchEventListener {
        private static final int PALETTE_COLUMNS = 7;
        private static final int PALETTE_ROWS = 10;

        private static final int PALETTE_FIELD_WIDTH = 50;
        private static final int PALETTE_FIELD_HEIGHT = 50;
        private static final float GRID_STROKE_WIDTH = 5;

        private final BasicFieldColorView mParent;
        private final Paint mAreaPaint = new Paint();
        private final Paint mGridPaint = new Paint();

        // From https://github.com/google/closure-library/blob/master/closure/goog/ui/colorpicker.js
        // TODO(#70): move this table into resources.
        private final int[] mColorArray = new int[]{
                // grays
                0xffffffff, 0xffcccccc, 0xffc0c0c0, 0xff999999, 0xff666666, 0xff333333, 0xff000000,
                // reds
                0xffffcccc, 0xffff6666, 0xffff0000, 0xffcc0000, 0xff990000, 0xff660000, 0xff330000,
                // oranges
                0xffffcc99, 0xffff9966, 0xffff9900, 0xffff6600, 0xffcc6600, 0xff993300, 0xff663300,
                // yellows
                0xffffff99, 0xffffff66, 0xffffcc66, 0xffffcc33, 0xffcc9933, 0xff996633, 0xff663333,
                // olives
                0xffffffcc, 0xffffff33, 0xffffff00, 0xffffcc00, 0xff999900, 0xff666600, 0xff333300,
                // greens
                0xff99ff99, 0xff66ff99, 0xff33ff33, 0xff33cc00, 0xff009900, 0xff006600, 0xff003300,
                // turquoises
                0xff99ffff, 0xff33ffff, 0xff66cccc, 0xff00cccc, 0xff339999, 0xff336666, 0xff003333,
                // blues
                0xffccffff, 0xff66ffff, 0xff33ccff, 0xff3366ff, 0xff3333ff, 0xff000099, 0xff000066,
                // purples
                0xffccccff, 0xff9999ff, 0xff6666cc, 0xff6633ff, 0xff6600cc, 0xff333399, 0xff330099,
                // violets
                0xffffccff, 0xffff99ff, 0xffcc66cc, 0xffcc33cc, 0xff993399, 0xff663366, 0xff330033
        };

        ColorPaletteView(BasicFieldColorView parent) {
            super(parent.getContext());
            addDrawTask(this);
            setEstimateSizeListener(this);
            setTouchEventListener(this);

            mParent = parent;
            mGridPaint.setColor(Color.DKGRAY);
            mGridPaint.setStrokeWidth(GRID_STROKE_WIDTH);
            mGridPaint.setStyle(Paint.Style.STROKE_STYLE);
        }

        @Override
        public int getMinWidth() {
            return PALETTE_FIELD_WIDTH * PALETTE_COLUMNS;
        }

        @Override
        public int getMinHeight() {
            return PALETTE_FIELD_HEIGHT * PALETTE_ROWS;
        }

        @Override
        public void onDraw(Component component, Canvas canvas) {
            drawPalette(canvas);
            drawGrid(canvas);
        }

        @Override
        public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
            int widthConfig = EstimateSpec.getSizeWithMode(getMinWidth(), EstimateSpec.PRECISE);
            int heightConfig = EstimateSpec.getSizeWithMode(getMinHeight(), EstimateSpec.PRECISE);
            setEstimatedSize(widthConfig, heightConfig);
            return true;
        }

        @Override
        public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
            MmiPoint point = touchEvent.getPointerPosition(touchEvent.getIndex());
            if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_DOWN) {
                int i = Math.min(PALETTE_COLUMNS - 1,
                        (int) point.getX() / PALETTE_FIELD_WIDTH);
                int j = Math.min(PALETTE_ROWS - 1,
                        (int) point.getY() / PALETTE_FIELD_HEIGHT);

                int index = i + j * PALETTE_COLUMNS;
                ShapeElement shapeElement = new ShapeElement();
                shapeElement.setRgbColor(RgbColor.fromArgbInt(mColorArray[index]));
                mParent.setBackground(shapeElement);
                mParent.mColorField.setColor(mColorArray[index]);
                mParent.mColorPopupWindow.hide();
                return true;
            }

            return false;
        }

        private void drawPalette(Canvas canvas) {
            int paletteIndex = 0;
            for (int j = 0; j < PALETTE_ROWS; ++j) {
                int y = j * PALETTE_FIELD_HEIGHT;
                for (int i = 0; i < PALETTE_COLUMNS; ++i, ++paletteIndex) {
                    int x = i * PALETTE_FIELD_WIDTH;
                    mAreaPaint.setColor(new Color(mColorArray[paletteIndex]));
                    canvas.drawRect(
                            x, y, x + PALETTE_FIELD_WIDTH, y + PALETTE_FIELD_HEIGHT, mAreaPaint);
                }
            }
        }

        private void drawGrid(Canvas canvas) {
            int width = getWidth();
            int height = getHeight();

            canvas.drawRect(0, 0, width - 1, height - 1, mGridPaint);
            for (int j = 0; j < PALETTE_ROWS; ++j) {
                int y = j * PALETTE_FIELD_HEIGHT;
                canvas.drawLine(0, y, width - 1, y, mGridPaint);
            }
            for (int i = 0; i < PALETTE_COLUMNS; ++i) {
                int x = i * PALETTE_FIELD_WIDTH;
                canvas.drawLine(x, 0, x, height - 1, mGridPaint);
            }
        }
    }

    PopupDialog getColorPopupWindow() {
        return mColorPopupWindow;
    }
}
